// 负责解释指令和插值表达式
class Compiler {
  constructor(vm) {
    this.vm = vm; // vue 实例
    this.el = vm.$el; //模板
    this.compile(this.el);
  }

  // 编译模版
  // 处理文本节点和元素节点
  compile(el) {
    //1. 循环遍历模板下的所有子元素
    const nodes = el.childNodes;

    Array.from(nodes).forEach((node) => {
      // 2. 判断节点是文本节点还是元素节点
      if (this.isTextNode(node)) {
        this.compileText(node);
      } else if (this.isElementNode(node)) {
        this.compileElement(node);
      }

      // 3. 如果当前的节点中还存在自节点，递归编译
      if (node.childNodes && node.childNodes.length) {
        this.compile(node);
      }
    });
  }

  // 编译文本节点 编译插值表达
  compileText(node) {
    // 获取文本节点内容
    const reg = /\{\{(.+?)\}\}/; // 插值表达式正则
    const value = node.textContent; // 获取标签的文本内容，也就是代码中所写的插值表达式 <h1>{{msg}}</h1>
    // 判断是否是插值表达式
    if (reg.test(value)) {
      const key = RegExp.$1.trim(); // 插值表达式中的值就是我们需要的属性名称
      node.textContent = value.replace(reg, this.vm[key]); // 根据去除的这个属性名冲vue 实例上获取值替换掉插值表达式

      // 穿件watcher 当数据改变时更新试图
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue;
      });
    }
  }

  // 编译属性节点
  // 处理元素上的指令
  compileElement(node) {
    // 遍历节点上所有的属性找到指令并解析
    const attributes = node.attributes;
    Array.from(attributes).forEach((attr) => {
      console.log(attr);
      // 获取当前元素属性的名称
      let attrName = attr.name;

      let eventName = ""; // 存储事件名称
      // 判断这个属性名是否为指令
      if (this.isDirective(attrName)) {
        // 如果是指令
        // 将v-xxxx的指令形式转换成 xxx，
        
        // 如果是v-on 或者 简写@xxx
        if (attrName.includes("v-on")) {
          eventName = attrName.substr(5);
          attrName = "on";
        } else if (attrName.includes("@")) {
          eventName = attrName.substr(1);
          attrName = "on";
        } else {
          attrName = attrName.substr(2); // 类似v-text ====>text  v-model ====> model
        }

        // 根据指令设置的值，即属性，也就是数据对象的属性 类似这里的msg ，counnt，他们被存放在元素的value 上
        const key = attr.value;
        // 处理不同的指令
        this.update(node, key, attrName, eventName);
      }
    });
  }

  /**
   *
   * @param {*} node 节点
   * @param {*} key 数据对象的属性名，就是指令的值
   * @param {*} dir 指令名称的后半部分 v-model 的model
   */
  update(node, key, dir, eventName) {
    const updaterFn = this[dir + "Updater"]; // 根据指令后半部分的名称 抽象更新方法，统一调用不同指令的处理方法
    console.dir(node, node.textContent);
    updaterFn && updaterFn.call(this, node, this.vm[key], key, eventName); // 获取对象属性的值并传递给更新方法
  }

  //   v-text 指令的更新方法
  textUpdater(node, value, key) {
    console.dir(node, value);
    node.textContent = value;
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue;
    });
  }

  // v-model指令的更新方法
  modelUpdater(node, value, key) {
    node.value = value;
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue;
    });

    // 双向绑定

    node.addEventListener("input", () => {
      this.vm[key] = node.value;
    });
  }
  // v-html指令
  htmlUpdater(node, value, key) {
    node.innerHTML = value;
    new Watcher(this.vm, key, (newValue) => {
      node.innerHTML = newValue;
    });
  }

  // 自定义事件v-on指令
  onUpdater(node, value, key, eventName) {
    console.log(node, value, key, eventName);
    node.addEventListener(eventName, () => {
      let fn = this.vm.$options?.methods?.[key];
      if (fn) {
        fn.call(this);
      } else {
        eval(key);
      }
    });
  }

  // 判断是否为文本节点
  isTextNode(node) {
    return node.nodeType === 3;
  }

  // 判断是否为元素节点
  isElementNode(node) {
    return node.nodeType === 1;
  }

  // 判断是否为v-开头的指令,或者@开头的
  isDirective(attrName) {
    return attrName.startsWith("v-") ||attrName.startsWith('@') ;
  }
}
